package org.spring.bean.factory.xml;

import cn.hutool.core.util.XmlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring.bean.exceptions.BeanDefinitionStoreException;
import org.spring.bean.factory.support.AbstractBeanDefinitionReader;
import org.spring.bean.factory.support.BeanDefinitionRegistry;
import org.spring.bean.factory.support.DefaultListableBeanFactory;
import org.spring.core.io.Resource;
import org.spring.core.io.support.EncodedResource;
import org.tool.core.lang.Assert;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;

/**
 * XML bean 定义的 bean 定义阅读器。
 * 将实际的 XML 文档读取委托给 {@link BeanDefinitionDocumentReader} 接口的实现。
 *
 * <p>通常应用于 {@link DefaultListableBeanFactory} 或 {@link GenericApplicationContext}。
 *
 * <p>此类加载一个 DOM 文档并将 BeanDefinitionDocumentReader 应用到它。
 * 文档阅读器将向给定的 bean 工厂注册每个 bean 定义，并与后者的 {@link BeanDefinitionRegistry} 接口的实现进行对话。
 *
 * @author 李坤
 * @date 2022/4/23 12:56
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    private final Logger logger = LoggerFactory.getLogger(XmlBeanDefinitionReader.class);

    private final BeanDefinitionDocumentReader beanDefinitionDocumentReader = new DefaultBeanDefinitionDocumentReader();

    public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
        super(beanDefinitionRegistry);
    }

    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        Assert.notNull(resource, "Resource must not be null");

        return loadBeanDefinitions(new EncodedResource(resource));
    }

    @Override
    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        int loadedBeanDefinitions = 0;
        for (Resource resource : resources) {
            loadedBeanDefinitions += loadBeanDefinitions(resource);
        }
        return loadedBeanDefinitions;
    }

    @Override
    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        Assert.hasText(location, "Location must not be empty");

        final Resource resource = getResourceLoader().getResource(location);
        return loadBeanDefinitions(resource);
    }

    @Override
    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        int loadedBeanDefinitions = 0;
        for (String location : locations) {
            loadedBeanDefinitions += loadBeanDefinitions(location);
        }
        return loadedBeanDefinitions;
    }

    /*------------------------------------------------protected methods-----------------------------------------------*/

    /**
     * 从指定的 XML 文件加载 bean 定义。
     *
     * @param encodedResource XML 文件的资源描述符，允许指定用于解析文件的编码
     *
     * @return 找到的 bean 定义的数量
     *
     * @throws BeanDefinitionStoreException 在加载或解析错误的情况下
     */
    protected int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");

        try (final InputStream inputStream = encodedResource.getResource().getInputStream()) {
            final InputSource inputSource = new InputSource(inputStream);

            if (Objects.nonNull(encodedResource.getEncoding())) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            return registerBeanDefinitions(XmlUtil.readXML(inputSource));
        } catch (IOException exception) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource());
        }
    }

    /**
     * 注册包含在给定 DOM 文档中的 bean 定义。由 {@code loadBeanDefinitions} 调用。
     * <p>创建解析器类的新实例并在其上调用 {@code registerBeanDefinitions}。
     *
     * @param document DOM 文档
     *
     * @return 找到的 bean 定义的数量
     *
     * @throws BeanDefinitionStoreException 在解析错误的情况下
     * @see #loadBeanDefinitions
     */
    public int registerBeanDefinitions(Document document) throws BeanDefinitionStoreException {
        final BeanDefinitionRegistry registry = getBeanDefinitionRegistry();
        final int befBeanDefinitions = registry.getBeanDefinitionCount();

        XmlReaderContext readerContext = new XmlReaderContext(this);
        beanDefinitionDocumentReader.registerBeanDefinitions(document, readerContext);

        return registry.getBeanDefinitionCount() - befBeanDefinitions;
    }

}
